package nayasis.common.db.manager.connection;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import nayasis.common.conf.NConfigulator;
import nayasis.common.db.manager.etc.DriverManager;
import nayasis.common.db.manager.transaction.ConnectionThreadLocal;
import nayasis.common.db.manager.transaction.TxManager;
import nayasis.common.exception.unchecked.DatabaseException;

import org.jdom2.Element;
import org.jdom2.JDOMException;

/**
 * DB 커넥션을 관리한다.
 *
 * @author nayasis
 */
public abstract class ConnectionManager {

    private ConnectionManager() {}

    /**
	 * Connection Pool 저장소
	 */
	private static Map<String, ConnectionPool> connectionPoolMap = new HashMap<>();

	/**
	 * Connection Pool을 만들어 캐시용 저장소에 저장한다.
	 *
	 * @param dbName database manager 이름
	 * @throws DatabaseException dbName에 해당하는 database manager가 common.xml 파일에 정의되어 있지 않을 때
	 * @throws JDOMException 
	 * @throws IOException 
	 */
	private static void setConnectionPool( String dbName ) throws IOException, JDOMException {

		Element node = getElement( String.format("/common/database/manager[@name='%s']", dbName) );
		
		DriverManager.registerDriver( NConfigulator.getValue(node.getChild("class")) );
		
		ConnectionPool cp = new ConnectionPool(
				dbName,
				NConfigulator.getValue( node.getChild("url"     ) ),
				NConfigulator.getValue( node.getChild("id"      ) ),
				NConfigulator.getValue( node.getChild("password") ) );
		
        connectionPoolMap.put( dbName, cp );

	}

	private static Element getElement( String xPath ) {

		Element node = NConfigulator.getElement( xPath );
		
		if( node == null ) {
			throw new DatabaseException( "There is no configuration about databaseManager in common.xml. [@]", xPath );
		}
		
		return node;
		
	}
	
	/**
	 * 캐시용 저장소에 저장된 WAS의 DataSource를 가져온다.
	 *
	 * @param dbName WAS의 DB resource 이름
	 * @return 사용하고자 하는 DataSource
	 * @exception WAS에 DB resource 설정이 누락되었거나 lookup할 이름이 잘못 설정되어있을 경우
	 */
	private static ConnectionPool getConnectionPool( String dbName ) {

	    if( ! connectionPoolMap.containsKey(dbName) ) {
	        try {
                setConnectionPool( dbName );
            } catch (IOException e) {
				e.printStackTrace();
			} catch (JDOMException e) {
				e.printStackTrace();
			}
	    }

		return connectionPoolMap.get( dbName );
	}

	/**
	 * DB Connection을 가져온다.
	 *
	 * @param dbSpec DB spec 명
	 * @return DB Connection
	 */
	public static WrapperConnection getConnection( String dbSpec ) {

		// DB Spec이 없으면 default 값을 선택한다.
		if( dbSpec == null ) {
			
			Element node = getElement( "/common/database/manager[@default='true']" );
			
			dbSpec = node.getAttributeValue( "name" );
			
			if( dbSpec == null ) {
				node = getElement( "/common/database/manager[1]" );
				dbSpec = node.getAttributeValue( "name" );
			}

		}
		
		// 트렌젝션 활성화상태라면, 트렌젝션에 사용되었던 Connection을 계속해서 그대로 전달해준다.
		if( TxManager.isTransactionWorking() && ConnectionThreadLocal.contains(dbSpec) ) {
			return ConnectionThreadLocal.get( dbSpec );
		}

		// DataSource에서 Pooling DB Connection을 하나 가져온다.
		WrapperConnection connection = getConnectionPool( dbSpec ).getConnection();
		
		// 트렌젝션 활성화상태이면서, 신규로 Connection을 사용하고자 한다면,
		// 추후 계속해서 같은 Connection을 사용해주기 위해
		// ThreadLocal에 Connection을 저장해둔다.
		if( TxManager.isTransactionWorking() ) {
			ConnectionThreadLocal.set( connection );
		}

		return connection;

	}

	/**
	 * DB Connection을 가져온다.
	 *
	 * @return DB Connection
	 */
	public static WrapperConnection getConnection() {

		return getConnection( null );
	}

	/**
	 * DB Connection을 닫는다.
	 *
	 * @param connection 연결을 종료하고자 하는 DB Connection
	 * @return Connection 반납 성공여부
	 */
	public static boolean closeConnection( WrapperConnection connection ) {

		// Transaction 활성화 상태라면 Connection을 반납하지 않는다.
		if( TxManager.isTransactionWorking() && ConnectionThreadLocal.contains(connection) ) return false;

		// Connection Pool에 Connection을 반납한다.
		ConnectionPool connectionPool = getConnectionPool( connection.getDbSpec() );
		connectionPool.closeConnection( connection );
		return true;

	}

}
